package ngat.oss;

import java.util.*;
import java.text.*;
import java.io.*;

import jyd.storable.*;
import jyd.collection.*;

import ngat.phase2.*;
import ngat.astrometry.*;
import ngat.util.*;
import ngat.util.logging.*;


/** Calculate the 'true' contention measure over an observing night.*/

public class TestCalculateContention {

    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    public static final double VERY_SMALL = Math.toRadians(0.1);

    public static final long ONE_DAY = 24*3600*1000L;

    Root root;
    
    Logger logger;
    
    public double domeLimit;
    public double wgtPriority;
    public double wgtLunar;
    public double wgtSeeing;

    List candidates; 
    List cache;
    
    /** Prime the cache from DB.*/
    public TestCalculateContention(Root root) throws Exception {
	this.root = root;
	logger = LogManager.getLogger("OSS");
	
	cache = new Vector();
	candidates = new Vector();

	// Prime the cache.
	Iterator iTag = root.listAllTags();
	while (iTag.hasNext()) {
	    
	    Tag tag = (Tag)iTag.next();
	    
	    Iterator iUser = tag.listAllUsers();
	    while (iUser.hasNext()) {
		
		User user = (User)iUser.next();
		
		Iterator iProp = user.listAllProposals();
		while (iProp.hasNext()) {
		    
		    KVPair pair = (KVPair)iProp.next();
		    String name = (String)pair.getKey();
		    Path   path = new Path(user.getFullPath(), name);
		    
		    try {

			Proposal proposal = OSS_Utilities.fetchProposal(path);
			
			if (proposal == null) continue;
			
			cache.add(proposal);
			
		    } catch (Exception e) {
			// error loading a proposal....
			e.printStackTrace();
		    }
		}
	    }
	}
    }
    
    /** Calculate the contention stats for night time period.
     * @param startNight The start of the night (nominally sunset).
     * @param endNight The end of the night (nominally sunrise).
     * @param resolution Bin resolution (mins).
     */
    public double[] calculateContention(long startNight, long endNight, int resolution) {
	
	int nn = (int)(endNight-startNight)/(1000*60*resolution)+1;

	TimeWindow tonight = new TimeWindow(startNight, endNight);

	double[] contention = new double[nn];
	for (int i = 0; i < nn; i++) {
	    contention[i] = 0.0;
	}

	Iterator ip = cache.iterator();
	while (ip.hasNext()) {
	    
	    try {
		
		Proposal proposal = (Proposal)ip.next();
		
		if (proposal == null) continue;
		
		logger.log(3, "Scheduler", "Checking Proposal: "+proposal.getFullPath());
		
		// Outside activation period.
		logger.log(3, "Scheduler", "Checking activation period...");
		if (proposal.getActivationDate() > endNight ||
		    proposal.getExpiryDate() < startNight) {
		    logger.log(3, "Scheduler", "Proposal outside activation period: "+
			       sdf.format(new Date(proposal.getActivationDate()))+" - "+
			       sdf.format(new Date(proposal.getExpiryDate())));			
		    continue;
		}		    
		
		// No time left.
		logger.log(3, "Scheduler", "Checking for time left...");
		if (proposal.getUsedTime() >= proposal.getAllocatedTime()) {
		    logger.log(3, "Scheduler", "Proposal out of time: Used: "+proposal.getUsedTime()+
			       " of "+proposal.getAllocatedTime());
		    continue;
		}
		
		Iterator iGroup = proposal.listAllGroups();
		logger.log(3, "Scheduler", "Checking groups...");
		
		while (iGroup.hasNext()) {
		    
		    Group group = (Group)iGroup.next();
		    		 
		    // get all the group's time windows which intersect the specified night.
		    // we dont want the intersect yet as we need to calculate the size of the interval..
		    TimeWindow[] windows = getGroupWindows(group, startNight, endNight);

		    if (windows != null) {
			for (int j = 0; j < windows.length; j++) {
			    if (! windows[j].equals(TimeWindow.EMPTY_WINDOW)) {
				logger.log(2, "Scheduler", "Checking group "+group.getName()+"/ Window: "+windows[j]);

				// how much of this window is actually usable by group ?

				// choose a search subwindow size - not too small.
				long dt =  (windows[j].end - windows[j].start)/ 10000;
				if (dt < 600000L)
				    dt = 600000L;

				long usable = 0L;

				long sswin = windows[j].start;
				while (sswin < windows[j].end) {
				    if (canDo(group, sswin))
					usable += dt;
				    sswin += dt;
				}
				logger.log(2, "Scheduler", "Usable time for window = "+usable);

				if (usable <= 0.0) continue;
				
				double exec = getExecTime(group);
				logger.log(2, "Scheduler", "Exec time for group: "+exec);

				double contribution = exec/usable;

				// now go over the period where window intersects with tonight.
				
				TimeWindow overlap = windows[j].intersect(tonight);
				if (! overlap.equals(TimeWindow.EMPTY_WINDOW)) {
				    
				    // here is the first/last array elements to fill
				    int ics = (int)(overlap.start - tonight.start)/(1000*60*resolution);
				    int ice = (int)(overlap.end   - tonight.start)/(1000*60*resolution);
				    
				    for (int ic = ics; ic < ice; ic++) {				    
					contention[ic] += contribution;
				    }

				}

			    }
			}
		    }
		    // Check each time-slot between start and end with resolution.
		   
		    
		} // next (group)

	    } catch (Exception e) {
		// error processing a proposal....
		e.printStackTrace();
	    }
	    
	} // next (proposal)
	
	return contention;

    }

    /** Work out intersection of group's windows with period.*/
    private TimeWindow[] getGroupWindows(Group group, long start, long end) {

	TimeWindow night = new TimeWindow(start,end);
	// Work out the group's available windows..

	//System.err.println("Check windows for: "+group);		    

	if (group instanceof MonitorGroup) {
	    // MONITOR
	    MonitorGroup mg = (MonitorGroup)group;
	    int nw = (int)((mg.getEndDate() - mg.getStartDate())/mg.getPeriod());
	  
	    List wins = new Vector();

	    long win2 = (long)(0.5*(double)mg.getPeriod()*mg.getFloatFraction());

	    for (int k = 0; k < nw; k++) {
		long ws = mg.getStartDate() + k*mg.getPeriod() - win2;
		long we = mg.getStartDate() + k*mg.getPeriod() + win2;
		TimeWindow win = new TimeWindow(ws,we);
		if (! night.intersect(win).equals(TimeWindow.EMPTY_WINDOW))
		    wins.add(win);
	    }
	    TimeWindow[] tw = new TimeWindow[wins.size()];

	    for (int p = 0; p < wins.size(); p++) {
		tw[p] = (TimeWindow)wins.get(p);
	    }

	    return tw;

	} else if 
	    (group instanceof FixedGroup) {
	    // FIXED
	    
	} else if
	    (group instanceof EphemerisGroup) {
	    // EPHEMERIS
	    
	} else {
	    // FLEXIBLE
	    //long fgs = group.getEntryDate();
	  
	    long fge = group.getExpiryDate();
	    long fgs = fge - 60*86400*1000L;
	    TimeWindow fgw = new TimeWindow(fgs,fge);
	    if (! night.intersect(fgw).equals(TimeWindow.EMPTY_WINDOW)) 
		return new TimeWindow[] {fgw};
	    // return the actual window, not the intersect, but we DO need to test for intersect.
	} 

	return  new TimeWindow[] {TimeWindow.EMPTY_WINDOW};
    }

    /** Returns tue if group can be executed at time.*/
    public boolean canDo(Group group, long time) {
	
	if (group.getExpiryDate() < time)
	    return false;
	
	if (group instanceof MonitorGroup) {
	    MonitorGroup mg = (MonitorGroup)group;
	    long   startDate     = mg.getStartDate();
	    long   period        = mg.getPeriod();
	    float  floatFraction = mg.getFloatFraction();
	    
	    double fPeriod = (double)( time - startDate ) / (double)period;
	    double iPeriod = Math.rint(fPeriod);
	    
	    if (startDate > time)
		return false;
	    
	    long endDate = mg.getEndDate();
	    if (endDate <= time)
		return false;
		    
	    long startFloat = startDate + 
		(long)((iPeriod - (double)floatFraction / 2.0)* (double)period);
	    long endFloat   = startDate + 
		(long)((iPeriod + (double)floatFraction / 2.0)* (double)period);
	    
	    if ( ! ((startFloat <= time) && (endFloat >= time)) )
		return false;
	    
	} else if
	    (group instanceof EphemerisGroup) {
	    
	    EphemerisGroup eg = (EphemerisGroup)group;
	    
	    long   startDate     = eg.getStart();
	    long   endDate       = eg.getEnd();
	    long   period        = eg.getPeriod();
	    double phase         = (double)eg.getPhase();
	    double slopFraction  = (double)eg.getSlopPhase();
	    
	    double fperiod = Math.floor((time - startDate)/period);
	    int iperiod = (int)fperiod;
	    
	    if (startDate > time)
		return false;
		   
	    if (endDate <= time)
		return false;
	    
	    long startWindow = startDate + (long)((fperiod + phase - slopFraction/2.0) * period);
	    long endWindow   = startDate + (long)((fperiod + phase + slopFraction/2.0) * period);
	    
	    if ( ! ((startWindow <= time) && (endWindow >= time)))
		return false;

	} else if
	    (group instanceof FixedGroup) {
	    
	    FixedGroup fg = (FixedGroup)group;
	    
	    // FG can be done within 5 minutes of the fixed time.
	    long fixed = fg.getFixedTime();
	    if ((fixed < (time - 300000L)) ||
		(fixed > (time + 300000L)))
		return false;
	    
	} else {
	    
	    // we can always do a flexible group - thats why they are called flexible !
	    if (group.isDone())
		return false;
	    
	}
	
	// check the other constraints.
	
	// is seeing OK ?
	//	if (seeing < group.getMinimumSeeing())
	//    return false;
	
	// is moon up/down/close
	Position moon = Astrometry.getLunarPosition(time);
	boolean moonup = moon.getAltitude(time) > 0.0;
	if (moonup && group.getMinimumLunar() == Group.DARK) {
	    //System.err.println("Moon up fail: "+group.getFullPath());
	    return false;
	}
	
	// are all targets visible.
	int nobs = 0;
	Iterator iobs = group.listAllObservations();
	while (iobs.hasNext()) {
	    Observation obs = (Observation)iobs.next();
	   
	    Source src = obs.getSource();
	    Position target = src.getPosition();
	    
	    double elev = target.getAltitude(time);
	    
	    if (elev < domeLimit)
		return false;
	    nobs++;
	}
	if (nobs == 0)
	    return false;

	// can we do before sunrise-delta
	
	return true;
	
    }

 
    public double getExecTime(Group group) {
	
	double exec = 0.0;
	
	int nobs = 0;
	Iterator io = group.listAllObservations();
	while (io.hasNext()) {
	    
	    Observation obs = (Observation)io.next();
	    double expose = (double)obs.getExposeTime();
	    int nm = obs.getNumRuns();

	    exec += (expose+12000.0)*(double)nm;

	}
	exec += 60000.0; // slew
	exec += 30000.0; // rotate/settle

	return exec;
    }

    public static void main(String args[]) {
	
	try {
	    
	    Logger logger = LogManager.getLogger("OSS");
	    logger.setLogLevel(2);
	    LogHandler console = new ConsoleLogHandler(new BasicLogFormatter(150));
	    console.setLogLevel(3);
	    logger.addHandler(console);
	    
	    
	    CommandTokenizer parser = new CommandTokenizer("--");
	    parser.parse(args);
	    ConfigurationProperties config = parser.getMap();
	    
	    double lat = Math.toRadians(config.getDoubleValue("lat"));
	    double lon = Math.toRadians(config.getDoubleValue("long"));

	    Position.setViewpoint(lat,lon);
	    
	    System.setProperty("astrometry.impl", "ngat.astrometry.TestCalculator");
	    
	    File   rootDir  = new File(config.getProperty("jydodb"));
	    String rootName = config.getProperty("root");
	    
	    System.setProperty("jydodb", rootDir.getPath());
	    
	    System.err.println("Trying to locate root: "+rootName+" at "+rootDir.getPath());
	    
	    OSS_Utilities.setDatabaseDir(rootDir);	  
	    OSS_Utilities.setRootName(rootName);
	    
	    Root root = OSS_Utilities.fetchRoot();
	    
	    // Run  the test...   
	    long t1 = System.currentTimeMillis();
	    TestCalculateContention test = new TestCalculateContention(root);
	    long t2 = System.currentTimeMillis();
	    System.err.println("Loaded cache in "+(t2-t1)+" ms");
	    
	    test.domeLimit   = Math.toRadians(config.getDoubleValue("dome-limit", 20.0));
	    test.wgtPriority = config.getDoubleValue("wgt-priority");
	    test.wgtLunar    = config.getDoubleValue("wgt-lunar");
	    test.wgtSeeing   = config.getDoubleValue("wgt-seeing");
	    
	    // this is just some point in the day concerned - i.e. noonish !
	    Date date = sdf.parse(config.getProperty("time"));
	    Calendar cal = Calendar.getInstance();
	    cal.setTime(date);
	    int LH = 12 + (int)(Math.toDegrees(lon)/15.0);
	    if (LH > 24)
		LH -= 24;
	    if (LH < 0)
		LH += 24;

	    cal.set(Calendar.HOUR_OF_DAY, LH);
	    cal.set(Calendar.MINUTE, 0);
	    cal.set(Calendar.SECOND, 0);

	    long time = cal.getTime().getTime();
	    System.err.println("Reset zero time to : "+sdf.format(new Date(time)));

	    Position sun = Astrometry.getSolarPosition(time);	    
	    long sunset = sun.getSetTimeToday(time, 0.0);
	    long sunrise = sun.getRiseTimeToday(time+ONE_DAY, 0.0);

	    //if (sunset < time)
	    //	sunset += ONE_DAY;

	    // if (sunset - time > ONE_DAY)
	    //	sunset -= ONE_DAY;
	    
	    System.err.println("The night runs from: "+sdf.format(new Date(sunset))+"/"+sdf.format(new Date(sunrise))+
			       " A total of: "+((sunrise-sunset)/3600000.0)+"H" );
	    
	    int resolution = config.getIntValue("resolution", 10);

	    // now get the contention array back..	    
	    double[] cont = test.calculateContention(sunset, sunrise, resolution);

	    System.err.println("Got contention stats: ");
	    for (int i = 0; i < cont.length; i++) {
		long wt = sunset + i*resolution*60*1000L;
		System.err.println(sdf.format(new Date(wt))+" "+cont[i]);
	    }
	    
	} catch (Exception e) {
	    e.printStackTrace();
	    System.err.println("Usage: --jydodb <dir> --root <rootID> --lat <degs> --long <degs> --resolution <mins> "+
			       "--time(yyyy-MM-dd HH:mm:ss) --dome-limit <degs> --wgt-priority <w> --wgt-seeing <w> --wgt-lunar <w>");
	    return;
	}
	
	
    }

  
  

}
